home *** CD-ROM | disk | FTP | other *** search
/ MacWorld 2005 March / Macworld CD March 2005 - Marathon Trilogy.iso / Shareware World / iPod / iPodderX.sit / iPodderX / iPodderX.app / Contents / Resources / track.py < prev    next >
Encoding:
Python Source  |  2005-01-07  |  24.0 KB  |  517 lines

  1. # Written by Bram Cohen
  2. # see LICENSE.txt for license information
  3.  
  4. from parseargs import parseargs, formatDefinitions
  5. from RawServer import RawServer
  6. from HTTPHandler import HTTPHandler
  7. from NatCheck import NatCheck
  8. from threading import Event
  9. from bencode import bencode, bdecode, Bencached
  10. from zurllib import urlopen, quote, unquote
  11. from urlparse import urlparse
  12. from os import rename
  13. from os.path import exists, isfile
  14. from cStringIO import StringIO
  15. from time import time, gmtime, strftime
  16. from random import shuffle
  17. from sha import sha
  18. from types import StringType, IntType, LongType, ListType, DictType
  19. from binascii import b2a_hex, a2b_hex, a2b_base64
  20. import sys
  21. from __init__ import version
  22.  
  23. defaults = [
  24.     ('port', 80, "Port to listen on."),
  25.     ('dfile', None, 'file to store recent downloader info in'),
  26.     ('bind', '', 'ip to bind to locally'),
  27.     ('socket_timeout', 15, 'timeout for closing connections'),
  28.     ('save_dfile_interval', 5 * 60, 'seconds between saving dfile'),
  29.     ('timeout_downloaders_interval', 45 * 60, 'seconds between expiring downloaders'),
  30.     ('reannounce_interval', 30 * 60, 'seconds downloaders should wait between reannouncements'),
  31.     ('response_size', 50, 'number of peers to send in an info message'),
  32.     ('timeout_check_interval', 5,
  33.         'time to wait between checking if any connections have timed out'),
  34.     ('nat_check', 3,
  35.         "how many times to check if a downloader is behind a NAT (0 = don't check)"),
  36.     ('min_time_between_log_flushes', 3.0,
  37.         'minimum time it must have been since the last flush to do another one'),
  38.     ('allowed_dir', '', 'only allow downloads for .torrents in this dir'),
  39.     ('parse_allowed_interval', 15, 'minutes between reloading of allowed_dir'),
  40.     ('show_names', 1, 'whether to display names from allowed dir'),
  41.     ('favicon', '', 'file containing x-icon data to return when browser requests favicon.ico'),
  42.     ('only_local_override_ip', 1, "ignore the ip GET parameter from machines which aren't on local network IPs"),
  43.     ('logfile', '', 'file to write the tracker logs, use - for stdout (default)'),
  44.     ('allow_get', 0, 'use with allowed_dir; adds a /file?hash={hash} url that allows users to download the torrent file'),
  45.     ('keep_dead', 0, 'keep dead torrents after they expire (so they still show up on your /scrape and web page)'),
  46.     ('max_give', 200, 'maximum number of peers to give with any one request'),
  47.     ]
  48.  
  49. def statefiletemplate(x):
  50.     if type(x) != DictType:
  51.         raise ValueError
  52.     for cname, cinfo in x.items():
  53.         if cname == 'peers':
  54.             for y in cinfo.values():      # The 'peers' key is a dictionary of SHA hashes (torrent ids)
  55.                  if type(y) != DictType:   # ... for the active torrents, and each is a dictionary
  56.                      raise ValueError
  57.                  for id, info in y.items(): # ... of client ids interested in that torrent
  58.                      if (len(id) != 20):
  59.                          raise ValueError
  60.                      if type(info) != DictType:  # ... each of which is also a dictionary
  61.                          raise ValueError # ... which has an IP, a Port, and a Bytes Left count for that client for that torrent
  62.                      if type(info.get('ip', '')) != StringType:
  63.                          raise ValueError
  64.                      port = info.get('port')
  65.                      if type(port) not in (IntType, LongType) or port < 0:
  66.                          raise ValueError
  67.                      left = info.get('left')
  68.                      if type(left) not in (IntType, LongType) or left < 0:
  69.                          raise ValueError
  70.         elif cname == 'completed':
  71.             if (type(cinfo) != DictType): # The 'completed' key is a dictionary of SHA hashes (torrent ids)
  72.                 raise ValueError          # ... for keeping track of the total completions per torrent
  73.             for y in cinfo.values():      # ... each torrent has an integer value
  74.                 if type(y) not in (IntType, LongType):   # ... for the number of reported completions for that torrent
  75.                     raise ValueError
  76.  
  77. def parseTorrents(dir):
  78.     import os
  79.     a = {}
  80.     for f in os.listdir(dir):
  81.         if f[-8:] == '.torrent':
  82.             try:
  83.                 p = os.path.join(dir,f)
  84.                 d = bdecode(open(p, 'rb').read())
  85.                 h = sha(bencode(d['info'])).digest()
  86.                 i = d['info']
  87.                 a[h] = {}
  88.                 a[h]['name'] = i.get('name', f)
  89.                 a[h]['file'] = f
  90.                 a[h]['path'] = p
  91.                 l = 0
  92.                 if i.has_key('length'):
  93.                     l = i.get('length',0)
  94.                 elif i.has_key('files'):
  95.                     for li in i['files']:
  96.                         if li.has_key('length'):
  97.                             l = l + li['length']
  98.                 a[h]['length'] = l
  99.             except:
  100.                 # what now, boss?
  101.                 print "Error parsing " + f, sys.exc_info()[0]
  102.     return a
  103.  
  104. alas = 'your file may exist elsewhere in the universe\nbut alas, not here\n'
  105.  
  106. def isotime(secs = None):
  107.     if secs == None:
  108.         secs = time()
  109.     return strftime('%Y-%m-%d %H:%M UTC', gmtime(secs))
  110.  
  111. def compact_peer_info(ip, port):
  112.     return ''.join([chr(int(i)) for i in ip.split('.')]) + chr((port & 0xFF00) >> 8) + chr(port & 0xFF)
  113.  
  114. class Tracker:
  115.     def __init__(self, config, rawserver):
  116.         self.response_size = config['response_size']
  117.         self.dfile = config['dfile']
  118.         self.natcheck = config['nat_check']
  119.         self.max_give = config['max_give']
  120.         self.reannounce_interval = config['reannounce_interval']
  121.         self.save_dfile_interval = config['save_dfile_interval']
  122.         self.show_names = config['show_names']
  123.         self.only_local_override_ip = config['only_local_override_ip']
  124.         favicon = config['favicon']
  125.         self.favicon = None
  126.         if favicon:
  127.             if isfile(favicon):
  128.                 h = open(favicon, 'rb')
  129.                 self.favicon = h.read()
  130.                 h.close()
  131.             else:
  132.                 print "**warning** specified favicon file -- %s -- does not exist." % favicon
  133.         self.rawserver = rawserver
  134.         self.becache1 = {}
  135.         self.becache2 = {}
  136.         self.cache1 = {}
  137.         self.cache2 = {}
  138.         self.times = {}
  139.         if exists(self.dfile):
  140.             h = open(self.dfile, 'rb')
  141.             ds = h.read()
  142.             h.close()
  143.             tempstate = bdecode(ds)
  144.         else:
  145.             tempstate = {}
  146.         if tempstate.has_key('peers'):
  147.             self.state = tempstate
  148.         else:
  149.             self.state = {}
  150.             self.state['peers'] = tempstate
  151.         self.downloads = self.state.setdefault('peers', {})
  152.         self.completed = self.state.setdefault('completed', {})
  153.         statefiletemplate(self.state)
  154.         for x, dl in self.downloads.items():
  155.             self.times[x] = {}
  156.             for y, dat in dl.items():
  157.                 self.times[x][y] = 0
  158.                 if not dat.get('nat',1):
  159.                     ip = dat['ip']
  160.                     gip = dat.get('given ip')
  161.                     if gip and is_valid_ipv4(gip) and (not self.only_local_override_ip or is_local_ip(ip)):
  162.                         ip = gip
  163.                     self.becache1.setdefault(x,{})[y] = Bencached(bencode({'ip': ip, 
  164.                         'port': dat['port'], 'peer id': y}))
  165.                     self.becache2.setdefault(x,{})[y] = compact_peer_info(ip, dat['port'])
  166.         rawserver.add_task(self.save_dfile, self.save_dfile_interval)
  167.         self.prevtime = time()
  168.         self.timeout_downloaders_interval = config['timeout_downloaders_interval']
  169.         rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
  170.         self.logfile = None
  171.         self.log = None
  172.         if (config['logfile'] != '') and (config['logfile'] != '-'):
  173.             try:
  174.                 self.logfile = config['logfile']
  175.                 self.log = open(self.logfile,'a')
  176.                 sys.stdout = self.log
  177.                 print "# Log Started: ", isotime()
  178.             except:
  179.                 print "Error trying to redirect stdout to log file:", sys.exc_info()[0]
  180.         self.allow_get = config['allow_get']
  181.         if config['allowed_dir'] != '':
  182.             self.allowed_dir = config['allowed_dir']
  183.             self.parse_allowed_interval = config['parse_allowed_interval']
  184.             self.parse_allowed()
  185.         else:
  186.             self.allowed = None
  187.         if unquote('+') != ' ':
  188.             self.uq_broken = 1
  189.         else:
  190.             self.uq_broken = 0
  191.         self.keep_dead = config['keep_dead']
  192.  
  193.     def get(self, connection, path, headers):
  194.         try:
  195.             (scheme, netloc, path, pars, query, fragment) = urlparse(path)
  196.             if self.uq_broken == 1:
  197.                 path = path.replace('+',' ')
  198.                 query = query.replace('+',' ')
  199.             path = unquote(path)[1:]
  200.             params = {}
  201.             for s in query.split('&'):
  202.                 if s != '':
  203.                     i = s.index('=')
  204.                     params[unquote(s[:i])] = unquote(s[i+1:])
  205.         except ValueError, e:
  206.             return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 
  207.                     'you sent me garbage - ' + str(e))
  208.         if path == '' or path == 'index.html':
  209.             s = StringIO()
  210.             s.write('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">\n' \
  211.                 '<html><head><title>BitTorrent download info</title>\n')
  212.             if self.favicon != None:
  213.                 s.write('<link rel="shortcut icon" href="/favicon.ico" />\n')
  214.             s.write('</head>\n<body>\n' \
  215.                 '<h3>BitTorrent download info</h3>\n'\
  216.                 '<ul>\n'
  217.                 '<li><strong>tracker version:</strong> %s</li>\n' \
  218.                 '<li><strong>server time:</strong> %s</li>\n' \
  219.                 '</ul>\n' % (version, isotime()))
  220.             names = self.downloads.keys()
  221.             if names:
  222.                 names.sort()
  223.                 tn = 0
  224.                 tc = 0
  225.                 td = 0
  226.                 tt = 0  # Total transferred
  227.                 ts = 0  # Total size
  228.                 nf = 0  # Number of files displayed
  229.                 uc = {}
  230.                 ud = {}
  231.                 if self.allowed != None and self.show_names:
  232.                     s.write('<table summary="files" border="1">\n' \
  233.                         '<tr><th>info hash</th><th>torrent name</th><th align="right">size</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th><th align="right">transferred</th></tr>\n')
  234.                 else:
  235.                     s.write('<table summary="files">\n' \
  236.                         '<tr><th>info hash</th><th align="right">complete</th><th align="right">downloading</th><th align="right">downloaded</th></tr>\n')
  237.                 for name in names:
  238.                     l = self.downloads[name]
  239.                     n = self.completed.get(name, 0)
  240.                     tn = tn + n
  241.                     lc = []
  242.                     for i in l.values():
  243.                         if type(i) == DictType:
  244.                             if i['left'] == 0:
  245.                                 lc.append(1)
  246.                                 uc[i['ip']] = 1
  247.                             else:
  248.                                 ud[i['ip']] = 1
  249.                     c = len(lc)
  250.                     tc = tc + c
  251.                     d = len(l) - c
  252.                     td = td + d
  253.                     if self.allowed != None and self.show_names:
  254.                         if self.allowed.has_key(name):
  255.                             nf = nf + 1
  256.                             sz = self.allowed[name]['length']  # size
  257.                             ts = ts + sz
  258.                             szt = sz * n   # Transferred for this torrent
  259.                             tt = tt + szt
  260.                             if self.allow_get == 1:
  261.                                 linkname = '<a href="/file?info_hash=' + quote(name) + '">' + self.allowed[name]['name'] + '</a>'
  262.                             else:
  263.                                 linkname = self.allowed[name]['name']
  264.                             s.write('<tr><td><code>%s</code></td><td>%s</td><td align="right">%s</td><td align="right">%i</td><td align="right">%i</td><td align="right">%i</td><td align="right">%s</td></tr>\n' \
  265.                                 % (b2a_hex(name), linkname, size_format(sz), c, d, n, size_format(szt)))
  266.                     else:
  267.                         s.write('<tr><td><code>%s</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td><td align="right"><code>%i</code></td></tr>\n' \
  268.                             % (b2a_hex(name), c, d, n))
  269.                 ttn = 0
  270.                 for i in self.completed.values():
  271.                     ttn = ttn + i
  272.                 if self.allowed != None and self.show_names:
  273.                     s.write('<tr><td align="right" colspan="2">%i files</td><td align="right">%s</td><td align="right">%i/%i</td><td align="right">%i/%i</td><td align="right">%i/%i</td><td align="right">%s</td></tr>\n'
  274.                             % (nf, size_format(ts), len(uc), tc, len(ud), td, tn, ttn, size_format(tt)))
  275.                 else:
  276.                     s.write('<tr><td align="right">%i files</td><td align="right">%i/%i</td><td align="right">%i/%i</td><td align="right">%i/%i</td></tr>\n'
  277.                             % (nf, len(uc), tc, len(ud), td, tn, ttn))
  278.                 s.write('</table>\n' \
  279.                     '<ul>\n' \
  280.                     '<li><em>info hash:</em> SHA1 hash of the "info" section of the metainfo (*.torrent)</li>\n' \
  281.                     '<li><em>complete:</em> number of connected clients with the complete file (total: unique IPs/total connections)</li>\n' \
  282.                     '<li><em>downloading:</em> number of connected clients still downloading (total: unique IPs/total connections)</li>\n' \
  283.                     '<li><em>downloaded:</em> reported complete downloads (total: current/all)</li>\n' \
  284.                     '<li><em>transferred:</em> torrent size * total downloaded (does not include partial transfers)</li>\n' \
  285.                     '</ul>\n')
  286.             else:
  287.                 s.write('<p>not tracking any files yet...</p>\n')
  288.             s.write('</body>\n' \
  289.                 '</html>\n')
  290.             return (200, 'OK', {'Content-Type': 'text/html; charset=iso-8859-1'}, s.getvalue())
  291.         elif path == 'scrape':
  292.             fs = {}
  293.             names = []
  294.             if params.has_key('info_hash'):
  295.                 if self.downloads.has_key(params['info_hash']):
  296.                     names = [ params['info_hash'] ]
  297.                 # else return nothing
  298.             else:
  299.                 names = self.downloads.keys()
  300.                 names.sort()
  301.             for name in names:
  302.                 l = self.downloads[name]
  303.                 n = self.completed.get(name, 0)
  304.                 c = len([1 for i in l.values() if type(i) == DictType and i['left'] == 0])
  305.                 d = len(l) - c
  306.                 fs[name] = {'complete': c, 'incomplete': d, 'downloaded': n}
  307.                 if (self.allowed is not None) and self.allowed.has_key(name) and self.show_names:
  308.                     fs[name]['name'] = self.allowed[name]['name']
  309.             r = {'files': fs}
  310.             return (200, 'OK', {'Content-Type': 'text/plain'}, bencode(r))
  311.         elif (path == 'file') and (self.allow_get == 1) and params.has_key('info_hash') and self.allowed.has_key(params['info_hash']):
  312.             hash = params['info_hash']
  313.             fname = self.allowed[hash]['file']
  314.             fpath = self.allowed[hash]['path']
  315.             return (200, 'OK', {'Content-Type': 'application/x-bittorrent', 'Content-Disposition': 'attachment; filename=' + fname}, open(fpath, 'rb').read())
  316.         elif path == 'favicon.ico' and self.favicon != None:
  317.             return (200, 'OK', {'Content-Type' : 'image/x-icon'}, self.favicon)
  318.         if path != 'announce':
  319.             return (404, 'Not Found', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, alas)
  320.         try:
  321.             if not params.has_key('info_hash'):
  322.                 raise ValueError, 'no info hash'
  323.             if params.has_key('ip') and not is_valid_ipv4(params['ip']):
  324.                 raise ValueError('DNS name or invalid IP address given for IP')
  325.             infohash = params['info_hash']
  326.             if self.allowed != None:
  327.                 if not self.allowed.has_key(infohash):
  328.                     return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode({'failure reason':
  329.                     'Requested download is not authorized for use with this tracker.'}))
  330.             ip = connection.get_ip()
  331.             ip_override = 0
  332.             if params.has_key('ip') and is_valid_ipv4(params['ip']) and (
  333.                     not self.only_local_override_ip or is_local_ip(ip)):
  334.                 ip_override = 1
  335.             if params.has_key('event') and params['event'] not in ['started', 'completed', 'stopped']:
  336.                 raise ValueError, 'invalid event'
  337.             port = long(params.get('port', ''))
  338.             uploaded = long(params.get('uploaded', ''))
  339.             downloaded = long(params.get('downloaded', ''))
  340.             left = long(params.get('left', ''))
  341.             myid = params.get('peer_id', '')
  342.             if len(myid) != 20:
  343.                 raise ValueError, 'id not of length 20'
  344.             rsize = self.response_size
  345.             if params.has_key('numwant'):
  346.                 rsize = min(long(params['numwant']), self.max_give)
  347.         except ValueError, e:
  348.             return (400, 'Bad Request', {'Content-Type': 'text/plain'}, 
  349.                 'you sent me garbage - ' + str(e))
  350.         peers = self.downloads.setdefault(infohash, {})
  351.         self.completed.setdefault(infohash, 0)
  352.         ts = self.times.setdefault(infohash, {})
  353.         confirm = 0
  354.         if peers.has_key(myid):
  355.             myinfo = peers[myid]
  356.             if myinfo.has_key('key'):
  357.                 if params.get('key') != myinfo['key']:
  358.                     return (200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, 
  359.                         bencode({'failure reason': 'key did not match key supplied earlier'}))
  360.                 confirm = 1
  361.             elif myinfo['ip'] == ip:
  362.                 confirm = 1
  363.         else:
  364.             confirm = 1
  365.         if params.get('event', '') != 'stopped' and confirm:
  366.             ts[myid] = time()
  367.             if not peers.has_key(myid):
  368.                 peers[myid] = {'ip': ip, 'port': port, 'left': left}
  369.                 if params.has_key('key'):
  370.                     peers[myid]['key'] = params['key']
  371.                 if params.has_key('ip') and is_valid_ipv4(params['ip']):
  372.                     peers[myid]['given ip'] = params['ip']
  373.                 mip = ip
  374.                 if ip_override:
  375.                     mip = params['ip']
  376.                 if not self.natcheck or ip_override:
  377.                     self.becache1.setdefault(infohash,{})[myid] = Bencached(bencode({'ip': mip, 'port': port, 'peer id': myid}))
  378.                     self.becache2.setdefault(infohash,{})[myid] = compact_peer_info(mip, port)
  379.             else:
  380.                 peers[myid]['left'] = left
  381.                 peers[myid]['ip'] = ip
  382.             if params.get('event', '') == 'completed':
  383.                 self.completed[infohash] = 1 + self.completed[infohash]
  384.             if port == 0:
  385.                 peers[myid]['nat'] = 2**30
  386.             elif self.natcheck and not ip_override:
  387.                 to_nat = peers[myid].get('nat', -1)
  388.                 if to_nat and to_nat < self.natcheck:
  389.                     NatCheck(self.connectback_result, infohash, myid, ip, port, self.rawserver)
  390.             else:
  391.                 peers[myid]['nat'] = 0
  392.         elif confirm:
  393.             if peers.has_key(myid):
  394.                 if self.becache1[infohash].has_key(myid):
  395.                     del self.becache1[infohash][myid]
  396.                     del self.becache2[infohash][myid]
  397.                 del peers[myid]
  398.                 del ts[myid]
  399.         data = {'interval': self.reannounce_interval}
  400.         if params.get('compact', 0):
  401.             if rsize == 0:
  402.                 data['peers'] = ''
  403.             else:
  404.                 cache = self.cache2.setdefault(infohash, [])
  405.                 if len(cache) < rsize:
  406.                     del cache[:]
  407.                     cache.extend(self.becache2.setdefault(infohash, {}).values())
  408.                     shuffle(cache)
  409.                     del self.cache1.get(infohash, [])[:]
  410.                 data['peers'] = ''.join(cache[-rsize:])
  411.                 del cache[-rsize:]
  412.         else:
  413.             if rsize == 0:
  414.                 data['peers'] = []
  415.             else:
  416.                 cache = self.cache1.setdefault(infohash, [])
  417.                 if len(cache) < rsize:
  418.                     del cache[:]
  419.                     cache.extend(self.becache1.setdefault(infohash, {}).values())
  420.                     shuffle(cache)
  421.                     del self.cache2.get(infohash, [])[:]
  422.                 data['peers'] = cache[-rsize:]
  423.                 del cache[-rsize:]
  424.         connection.answer((200, 'OK', {'Content-Type': 'text/plain', 'Pragma': 'no-cache'}, bencode(data)))
  425.  
  426.     def connectback_result(self, result, downloadid, peerid, ip, port):
  427.         record = self.downloads.get(downloadid, {}).get(peerid)
  428.         if record is None or record['ip'] != ip or record['port'] != port:
  429.             return
  430.         if not record.has_key('nat'):
  431.             record['nat'] = int(not result)
  432.         else:
  433.             if result:
  434.                 record['nat'] = 0
  435.             else:
  436.                 record['nat'] += 1
  437.         if result:
  438.             self.becache1.setdefault(downloadid,{})[peerid] = Bencached(bencode({'ip': ip, 'port': port, 'peer id': peerid}))
  439.             self.becache2.setdefault(downloadid,{})[peerid] = compact_peer_info(ip, port)
  440.  
  441.     def save_dfile(self):
  442.         self.rawserver.add_task(self.save_dfile, self.save_dfile_interval)
  443.         h = open(self.dfile, 'wb')
  444.         h.write(bencode(self.state))
  445.         h.close()
  446.  
  447.     def parse_allowed(self):
  448.         self.rawserver.add_task(self.parse_allowed, self.parse_allowed_interval * 60)
  449.         self.allowed = parseTorrents(self.allowed_dir)
  450.         
  451.     def expire_downloaders(self):
  452.         for x in self.times.keys():
  453.             for myid, t in self.times[x].items():
  454.                 if t < self.prevtime:
  455.                     if self.becache1.get(x, {}).has_key(myid):
  456.                         del self.becache1[x][myid]
  457.                         del self.becache2[x][myid]
  458.                     del self.times[x][myid]
  459.                     del self.downloads[x][myid]
  460.         self.prevtime = time()
  461.         if (self.keep_dead != 1):
  462.             for key, value in self.downloads.items():
  463.                 if len(value) == 0:
  464.                     del self.times[key]
  465.                     del self.downloads[key]
  466.         self.rawserver.add_task(self.expire_downloaders, self.timeout_downloaders_interval)
  467.  
  468. def is_valid_ipv4(ip):
  469.     try:
  470.         x = compact_peer_info(ip, 0)
  471.         if len(x) != 6:
  472.             return False
  473.     except (ValueError, IndexError):
  474.         return False
  475.     return True
  476.  
  477. def is_local_ip(ip):
  478.     try:
  479.         v = [long(x) for x in ip.split('.')]
  480.         if v[0] == 10 or v[0] == 127 or v[:2] in ([192, 168], [169, 254]):
  481.             return 1
  482.         if v[0] == 172 and v[1] >= 16 and v[1] <= 31:
  483.             return 1
  484.     except ValueError:
  485.         return 0
  486.  
  487. def track(args):
  488.     if len(args) == 0:
  489.         print formatDefinitions(defaults, 80)
  490.         return
  491.     try:
  492.         config, files = parseargs(args, defaults, 0, 0)
  493.     except ValueError, e:
  494.         print 'error: ' + str(e)
  495.         print 'run with no arguments for parameter explanations'
  496.         return
  497.     r = RawServer(Event(), config['timeout_check_interval'], config['socket_timeout'])
  498.     t = Tracker(config, r)
  499.     r.bind(config['port'], config['bind'], True)
  500.     r.listen_forever(HTTPHandler(t.get, config['min_time_between_log_flushes']))
  501.     t.save_dfile()
  502.     print '# Shutting down: ' + isotime()
  503.  
  504. def size_format(s):
  505.     if (s < 1024):
  506.         r = str(s) + 'B'
  507.     elif (s < 1048576):
  508.         r = str(int(s/1024)) + 'KiB'
  509.     elif (s < 1073741824l):
  510.         r = str(int(s/1048576)) + 'MiB'
  511.     elif (s < 1099511627776l):
  512.         r = str(int((s/1073741824.0)*100.0)/100.0) + 'GiB'
  513.     else:
  514.         r = str(int((s/1099511627776.0)*100.0)/100.0) + 'TiB'
  515.     return(r)
  516.  
  517.